package org.bultreebank.document_repository.impl;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

import org.bultreebank.document_repository.interfaces.IndexesDocumentsManager;
import org.bultreebank.document_repository.interfaces.StorageManager;
import org.bultreebank.index.exceptions.FileNotCreatedException;
import org.bultreebank.index.schema.management.interfaces.IndexingSchemaManager;
import org.bultreebank.index.utils.Constants;
import org.bultreebank.index.utils.Factory;
import org.bultreebank.index.utils.SystemInit;
import org.bultreebank.index.utils.Utils;
import org.bultreebank.search_engine.SearchEngineImpl;
import org.w3c.dom.Document;



public class IndexesDocumentsImpl implements IndexesDocumentsManager,
	Constants{

	private static final String INDEXES_DOCS_FILE = "indexes_documents.sys";
	private static final String DOC_IDs_FILE = "document_ids.sys";
	private static final String INDEX_DOCS_REPOSITORY = "IndexDocsRepository";
	private Map<String, List<String>> indexesDocsTable;
	private Properties systemDocIDs_realDocIDs;
	private Properties realDocIDs_systemDocIDs;
	private String searchEnginePath;
	private String indexDocsRepositoryPath;
	private StorageManager docStorageManager;
	private IndexingSchemaManager indexManager;
	
	public IndexingSchemaManager getIndexSchemaManager()
	{
		return indexManager;
	}
	
	public IndexesDocumentsImpl(IndexingSchemaManager manager,
			String storageClass, String storageLocation) throws Exception
	{
		indexManager = manager;
		searchEnginePath = manager.getSearchEngineRepositoryPath();
		init(searchEnginePath, indexManager);
		Map<String, Object> configMap = new HashMap<String, Object>();
		//if (storageClass != null && storageLocation != null && !storageClass.equals(EMPTY_STR) && !storageLocation.equals(EMPTY_STR))
		if (storageClass != null && !storageClass.equals(EMPTY_STR) )
		{
			if (storageClass.equals(SystemInit.storageDefaultClass))
				storageLocation = SearchEngineImpl.repositoryPath + SEPARATOR + INDEX_DOCS_REPOSITORY + SEPARATOR + storageLocation;
			Utils.createNewFolder(storageLocation);
			configMap.put(Factory.CLASS_NAME, storageClass);
			configMap.put(Factory.PARAM_TYPES, new Class[]{String.class});
			configMap.put(Factory.PARAM_IMPLS, new Object[]{storageLocation});
			docStorageManager = (StorageManager)Factory.instantiate(configMap);
		}
		
	}
	public StorageManager getStorageManager()
	{
		return docStorageManager;
	}
	/**
	 * Returns the path to the Document Repository if exists
	 * @return
	 */
	public String getIndexDocsRepositoryPath()
	{
		return indexDocsRepositoryPath;
	}
	
	/**
	 * Adds entries in the system document ID - real document ID table and 
	 * in the real index ID - system document IDs table. Saves the file in
	 * the Document Repository if it exists. 
	 * @param realDocID - document identifier defined by the user
	 * @param internalDocID - system generated unique document identifier 
	 * @param realIndexID - system generated unique index identifier
	 * @param doc - content of the document that is being indexed
	 * @throws Exception
	 */
	public void addDocumentToIndex(String realDocID, String internalDocID, 
			String realIndexID,
			Document doc) throws Exception {
		
		if (!indexManager.getAllUser_SystemIndexes().containsKey(realIndexID))
			throw new Exception ("There is not such an index " + realIndexID + 
					" available!");
		String internalIndexID = indexManager.getInternalIndexID(realIndexID);
		List<String> docsForIndex = indexesDocsTable.
			get(internalIndexID); 
		if (docsForIndex == null)
			docsForIndex = new ArrayList<String>();
		realDocIDs_systemDocIDs.put(realDocID, internalDocID);
		systemDocIDs_realDocIDs.put(internalDocID, realDocID);
		try
		{
			saveDocIDsFile();
		}
		catch (Exception e)
		{
			realDocIDs_systemDocIDs.remove(realDocID);
			systemDocIDs_realDocIDs.remove(internalDocID);
			throw new Exception("The saving of document IDs file " +
					"caused problems!");
		}
		
		if (internalDocID != null && !docsForIndex.contains(internalDocID))
		{
			docsForIndex.add(internalDocID);
			indexesDocsTable.put(internalIndexID, docsForIndex);
			try
			{
				saveIndexesDocsTable();
			}
			catch (Exception e)
			{
				docsForIndex.remove(internalDocID);
				indexesDocsTable.put(internalIndexID, docsForIndex);
				throw new Exception("The saving of indexes documents file " +
				"caused problems!");
			}
			if (docStorageManager != null)
				docStorageManager.addDocument(getInternalDocID(realDocID), doc);
		}
		
	}
	public void addDocumentToIndex(String realDocID,String realIndexID) throws Exception
	{
		if (!indexManager.getAllUser_SystemIndexes().containsKey(realIndexID))
			throw new Exception ("There is not such an index " + realIndexID + 
					" available!");
		String internalIndexID = indexManager.getInternalIndexID(realIndexID);
		String internalDocID = getInternalDocID(realDocID);
		if (internalDocID == null || internalDocID.equals(EMPTY_STR))
			throw new Exception ("The document: " + realDocID + " doesn't exist in the Search Engine Document Repository!");
		List<String> docsForIndex = indexesDocsTable.
			get(internalIndexID); 
		if (docsForIndex == null)
			docsForIndex = new ArrayList<String>();
		if (docsForIndex.contains(internalDocID))
			return;
		docsForIndex.add(internalDocID);
		indexesDocsTable.put(internalIndexID, docsForIndex);
		try
		{
			saveIndexesDocsTable();
		}
		catch (Exception e)
		{
			docsForIndex.remove(internalDocID);
			indexesDocsTable.put(internalIndexID, docsForIndex);
			throw new Exception("The saving of indexes documents file " +
			"caused problems!");
		}
		
	}
	/**
	 * Updates document identified by realDocID for the specified index,
	 * with its new content. This operation performs if a Document Repository
	 * exists.
	 * @param realDocID - document identifier defined by the user
	 * @param realIndexID - system generated unique index identifier
	 * @param doc - content of the document that is being indexed
	 * @throws Exception
	 */	
	public void updateDocumentToIndex(String realDocID, String realIndexID,
			Document doc) throws Exception {
		if (docStorageManager != null)
			docStorageManager.addDocument(getInternalDocID(realDocID), doc);
		
	}

	/**
	 * Deleted document id from the index - document IDs table
	 * @param realIndexID - index ID defined by the user
	 * @throws Exception
	 */
	public void deleteDocsForIndex(String realIndexID) throws Exception {
		
		
		if (!indexManager.getAllUser_SystemIndexes().containsKey(realIndexID))
			throw new Exception ("There is not such an index " + realIndexID + 
					" available!");
		String internalIndexID = indexManager.getInternalIndexID(realIndexID);
		List <String> docsForIndex = indexesDocsTable.get(internalIndexID);
		if (indexesDocsTable.containsKey(internalIndexID))
			indexesDocsTable.remove(internalIndexID);
		try
		{
			saveIndexesDocsTable();
		}
		catch (Exception e)
		{
			indexesDocsTable.put(internalIndexID, docsForIndex);
			throw new Exception("The saving of indexes documents file " +
			"caused problems!");
		}
		
		if (docStorageManager == null)
		{
			systemDocIDs_realDocIDs.clear();
			realDocIDs_systemDocIDs.clear();
			saveDocIDsFile();
		}
	}

	public void deleteDocumentForIndex(String realIndexID, String docID)
	throws Exception
	{
		if (!indexManager.getAllUser_SystemIndexes().containsKey(realIndexID))
			throw new Exception ("There is not such an index " + realIndexID + 
					" available!");
		String internalIndexID = indexManager.getInternalIndexID(realIndexID);
		List <String> docsForIndex = indexesDocsTable.get(internalIndexID);
		if (indexesDocsTable.containsKey(internalIndexID))
		{
			List<String> docs = indexesDocsTable.get(internalIndexID);
			if (docs.contains(getInternalDocID(docID)))
			{
				docs.remove(getInternalDocID(docID));
				indexesDocsTable.put(internalIndexID, docs);
				try
				{
					saveIndexesDocsTable();
				}
				catch (Exception e)
				{
					indexesDocsTable.put(internalIndexID, docsForIndex);
					throw new Exception("The saving of indexes documents file " +
					"caused problems!");
				}
			}
			
		}
		
	}
	/**
	 * Returns a list of document IDs added to the indexed identified
	 * by the realIndexID 
	 * @param realIndexID - index ID defined by the user
	 * @return
	 * @throws Exception
	 */
	public List<String> getDocsForIndex(String realIndexID) throws Exception {
		if (!indexManager.getAllUser_SystemIndexes().containsKey(realIndexID))
			throw new Exception ("There is not such an index " + realIndexID + 
					" available!");
		String internalIndexID = indexManager.getInternalIndexID(realIndexID);
		List<String> internalDocIDS = indexesDocsTable.get(internalIndexID);
		if (internalDocIDS == null)
			return new ArrayList<String>();
		List<String> realDocIDs = new ArrayList<String>();
		for (String internalID:internalDocIDS)
			realDocIDs.add(systemDocIDs_realDocIDs.getProperty(internalID));
		return realDocIDs;
	}
	
	/**
	 * Returns the document content of the document identified by docID,
	 * if a Document Repository exists
	 * @param docID - document ID
	 * @return
	 * @throws Exception
	 */
	public Document retrieveDocument(String docID) throws Exception {
		if (docStorageManager != null)
			return docStorageManager.retrieveDocument(getInternalDocID(docID));
		return null;
	}
	/**
	 * Returns properties object that define the system generated document IDs and
	 * their corresponding user defined document IDs analogs.
	 * @return
	 */
	public Properties getAllSystem_UserDocs() {
		return systemDocIDs_realDocIDs;
	}
	/**
	 * Returns properties object that define the user defined document IDs and
	 * their corresponding system generated document IDs analogs.
	 * @return
	 */
	public Properties getAllUser_SystemDocs() {
		return realDocIDs_systemDocIDs;
	}
	/**
	 * Returns the system generated document ID, corresponding to the 
	 * realDocID, as defined in the system document ID - real document ID
	 * table
	 * @param realDocID - document identifier defined by the user
	 * @return
	 * @throws Exception
	 */
	public String getInternalDocID(String userDocID) throws Exception {
		if (realDocIDs_systemDocIDs.isEmpty())
			return null;
		return realDocIDs_systemDocIDs.getProperty(userDocID);
	}
	/**
	 * Returns the user defined document ID, corresponding to the 
	 * internalDocID, as defined in the system document ID - real document ID
	 * table
	 * @param internalDocID - system generated unique document identifier 
	 * @return
	 * @throws Exception
	 */
	public String getUserDocID(String internalDocID) throws Exception {
		if (systemDocIDs_realDocIDs.isEmpty())
			return null;
		return systemDocIDs_realDocIDs.getProperty(internalDocID);
	}
	public static Map<String, List<String>> loadIndexesDocs(String searchRepPath) throws Exception
	{
		Map<String, List<String>> indexesDocsTable = new HashMap<String,
			List<String>>();
		String path = searchRepPath + SEPARATOR +
		INDEX_DOCS_REPOSITORY + SEPARATOR + INDEXES_DOCS_FILE;
		File f = Utils.createNewFile(path);
		if (f == null)
			throw new FileNotCreatedException("The file: " + path +
					" is not created!");
		BufferedReader reader = new BufferedReader(new FileReader(
				f));
		String line = reader.readLine();
		while (line != null)
		{
			int i = line.indexOf(WHITESPACE);
			String internalIndexID = line.substring(0, i);
			String fileIDs = line.substring(i + 1);
			StringTokenizer st = new StringTokenizer(fileIDs);
			List<String> filesList = new ArrayList<String>();
			while (st.hasMoreTokens())
			{
				String file = st.nextToken();
				filesList.add(file);
			}
			indexesDocsTable.put(internalIndexID, filesList);
			line = reader.readLine();
		}
		reader.close();
		return indexesDocsTable;
	}
	private void init(String searchEnginePath,
			IndexingSchemaManager indexManager)throws Exception
	{
		if (searchEnginePath == null || 
				searchEnginePath.equals(EMPTY_STR))
			throw new Exception("The path is not specified!");
		Utils.createNewFolder(searchEnginePath);
		this.searchEnginePath = searchEnginePath;
		this.indexDocsRepositoryPath = searchEnginePath + 
			SEPARATOR + INDEX_DOCS_REPOSITORY; 
		Utils.createNewFolder(indexDocsRepositoryPath); 
		systemDocIDs_realDocIDs = new Properties();
		realDocIDs_systemDocIDs = new Properties();
		indexesDocsTable = new HashMap<String, List<String>>();
		this.indexManager = indexManager;
		loadDocIDsFile();
		loadIndexesDocsTable();
	}
	
	private void loadIndexesDocsTable() throws Exception
	{
		String path = indexDocsRepositoryPath + SEPARATOR + 
		INDEXES_DOCS_FILE;
		File f = Utils.createNewFile(path);
		if (f == null)
			throw new FileNotCreatedException("The file: " + path +
					" is not created!");
		BufferedReader reader = new BufferedReader(new FileReader(
				f));
		String line = reader.readLine();
		
		
		while (line != null)
		{
			int i = line.indexOf(WHITESPACE);
			String internalIndexID = line.substring(0, i);
			String fileIDs = line.substring(i + 1);
			StringTokenizer st = new StringTokenizer(fileIDs);
			List<String> filesList = new ArrayList<String>();
			while (st.hasMoreTokens())
			{
				String file = st.nextToken();
				filesList.add(file);
			}
			indexesDocsTable.put(internalIndexID, filesList);
			line = reader.readLine();
		}
		reader.close();
	}

	private void saveIndexesDocsTable()
	    throws Exception
		{
		 	String path = indexDocsRepositoryPath + SEPARATOR + 
			INDEXES_DOCS_FILE;
		 	File f = new File(path);
		 	if (!f.delete())
		 		throw new Exception("The index-documents table cannot be modified!");
			Utils.createNewFile(path);
			PrintWriter out = new PrintWriter(new BufferedWriter(new 
					FileWriter(path)));
			Set<String> indexIDs = indexesDocsTable.keySet();
			Iterator<String> iter = indexIDs.iterator();
			while(iter.hasNext())
			{
				String indexID = iter.next();
				if (indexID != null)
				{
					List<String> indexedDocIDs = indexesDocsTable.
						get(indexID);
					StringBuffer buf = new StringBuffer();
					for (String doc:indexedDocIDs)
						buf.append(doc + WHITESPACE);
					String line = indexID + WHITESPACE + buf.toString() +
						NEW_LINE;
					out.print(line);
					out.flush();	
				}
			}
			out.close();
		}
	private void loadDocIDsFile()
	throws FileNotCreatedException,IOException
	{
		String path = indexDocsRepositoryPath + SEPARATOR + 
			DOC_IDs_FILE;
		Utils.loadTableFile(systemDocIDs_realDocIDs, 
				realDocIDs_systemDocIDs,
				path);
	}
	private void saveDocIDsFile() throws Exception
	{
		String path = indexDocsRepositoryPath + SEPARATOR +
			DOC_IDs_FILE;
		Utils.saveTableFile(path, systemDocIDs_realDocIDs);
	}
	
	
}
